/**
 * hw.c
 *
 *  Created on: 2018-05-11
 *      Author: chenshisheng
 */

#include "hw.h"
#include "stream/stream.h"
#include "stream/chprintf.h"
#include "gpio.h"
#include "shell.h"

#include <stdlib.h>
#include <string.h>
#include <ctype.h>

static const struct
{
    GPIO_TypeDef *port;
    uint16_t pin;
    HW_Level_t activeLevel;
    const char *name;
} _ins[] =
{
        {B1_GPIO_Port, B1_Pin,  HW_Level_Low, "User"},     // HW_In_User, KEY2
};

static const struct
{
    GPIO_TypeDef *port;
    uint16_t pin;
    HW_Level_t activeLevel;
    HW_State_t defaultStatus;
    const char *name;
}_outs[] =
{
        {LD2_GPIO_Port,     LD2_Pin,           HW_Level_High,  HW_State_Active,   "RUN"},      // HW_Out_Run,
};

static const struct
{
    uint16_t pin;
    uint32_t extiLine;
    IRQn_Type IRQn;
}_pinAttribs[] =
{
        {GPIO_PIN_0,  LL_EXTI_LINE_0,  EXTI0_IRQn},
        {GPIO_PIN_1,  LL_EXTI_LINE_1,  EXTI1_IRQn},
        {GPIO_PIN_2,  LL_EXTI_LINE_2,  EXTI2_IRQn},
        {GPIO_PIN_3,  LL_EXTI_LINE_3,  EXTI3_IRQn},
        {GPIO_PIN_4,  LL_EXTI_LINE_4,  EXTI4_IRQn},
        {GPIO_PIN_5,  LL_EXTI_LINE_5,  EXTI9_5_IRQn},
        {GPIO_PIN_6,  LL_EXTI_LINE_6,  EXTI9_5_IRQn},
        {GPIO_PIN_7,  LL_EXTI_LINE_7,  EXTI9_5_IRQn},
        {GPIO_PIN_8,  LL_EXTI_LINE_8,  EXTI9_5_IRQn},
        {GPIO_PIN_9,  LL_EXTI_LINE_9,  EXTI9_5_IRQn},
        {GPIO_PIN_10, LL_EXTI_LINE_10, EXTI15_10_IRQn},
        {GPIO_PIN_11, LL_EXTI_LINE_11, EXTI15_10_IRQn},
        {GPIO_PIN_12, LL_EXTI_LINE_12, EXTI15_10_IRQn},
        {GPIO_PIN_13, LL_EXTI_LINE_13, EXTI15_10_IRQn},
        {GPIO_PIN_14, LL_EXTI_LINE_14, EXTI15_10_IRQn},
        {GPIO_PIN_15, LL_EXTI_LINE_15, EXTI15_10_IRQn},
};

static const struct _Exti
{
    GPIO_TypeDef *port;
    uint16_t pinIndex;
    HW_Level_t activeLevel;
    uint32_t mode;
    const char *name;
}_extis[] =
{
        {GPIOF, 13,  HW_Level_Low, GPIO_MODE_IT_FALLING, "User"},        // HW_Exti_User, KEY2
};

static struct _ExtiCallback
{
    HW_ExtiCallback_t cb;
    int data;
}_extiCallbacks[16];

static void _ExtiDefaultHandler(int data);

void HW_Init(void)
{
    unsigned int i;
    GPIO_InitTypeDef GPIO_InitStruct = {0};

//    __HAL_RCC_GPIOA_CLK_ENABLE();
//    __HAL_RCC_GPIOB_CLK_ENABLE();
//    __HAL_RCC_GPIOC_CLK_ENABLE();
//    __HAL_RCC_GPIOD_CLK_ENABLE();
//    __HAL_RCC_GPIOE_CLK_ENABLE();
    __HAL_RCC_GPIOG_CLK_ENABLE();
    MX_GPIO_Init();

    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    for(i = 0; i < ARRAY_LEN(_ins); i++)
    {
        GPIO_InitStruct.Pin = _ins[i].pin;
        HAL_GPIO_Init(_ins[i].port, & GPIO_InitStruct);
    }

    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    for(i = 0; i < ARRAY_LEN(_outs); i++)
    {
        GPIO_InitStruct.Pin = _outs[i].pin;
        HAL_GPIO_Init(_outs[i].port, & GPIO_InitStruct);
        HW_Config(i, _outs[i].defaultStatus);
    }

//    GPIO_InitStruct.Pull = GPIO_NOPULL;
//    for(i = 0; i < ARRAY_LEN(_extis); i++)
//    {
//        GPIO_InitStruct.Mode = _extis[i].mode;
//        GPIO_InitStruct.Pin  = _pinAttribs[_extis[i].pinIndex].pin;
//        HAL_GPIO_Init(_extis[i].port, &GPIO_InitStruct);
//        HW_SetExtiCallback((HW_Exti_t)i, _ExtiDefaultHandler, (int) _extis[i].pinIndex);
//
//        /* EXTI interrupt init*/
//        HAL_NVIC_SetPriority(_pinAttribs[_extis[i].pinIndex].IRQn, 1, 0);
//        HAL_NVIC_EnableIRQ(_pinAttribs[_extis[i].pinIndex].IRQn);
//    }
}

void HW_DeInit(void)
{
    __HAL_RCC_GPIOE_CLK_DISABLE();
    __HAL_RCC_GPIOC_CLK_DISABLE();
    __HAL_RCC_GPIOH_CLK_DISABLE();
    __HAL_RCC_GPIOA_CLK_DISABLE();
    __HAL_RCC_GPIOB_CLK_DISABLE();
    __HAL_RCC_GPIOD_CLK_DISABLE();
}

__attribute__ ((unused))
static void _ExtiDefaultHandler(int data)
{
    HW_Exti_t exti;

    exti = (HW_Exti_t)data;
    MsgQueue_Send(MsgQueue_Id_ExtiTest, & exti, sizeof(exti));
}

void HW_OnExtiMsg(MsgQueue_Msg_t *msg)
{
    HW_Exti_t exti;

    exti = *((HW_Exti_t *)msg->data);
    printf("PIN%d Trigger\n", (int)exti);
}

void HW_Set(HW_Out_t out, HW_Level_t level)
{
    if (level != HW_Level_Low)
    {
        _outs[out].port->ODR |= _outs[out].pin;
    }
    else
    {
        _outs[out].port->ODR &= ~ _outs[out].pin;
    }
}

HW_Level_t HW_GetSetted(HW_Out_t out)
{
    return (_outs[out].port->ODR & _outs[out].pin) ? HW_Level_High : HW_Level_Low;
}

HW_Level_t HW_Get(HW_In_t in)
{
    return (_ins[in].port->IDR & _ins[in].pin) ? HW_Level_High : HW_Level_Low;
}

void HW_Toggle(HW_Out_t out)
{
    _outs[out].port->ODR ^= _outs[out].pin;
}

void HW_Config(HW_Out_t out, HW_State_t status)
{
    HW_Level_t level;

    level = status ? _outs[out].activeLevel : ! _outs[out].activeLevel;
    HW_Set(out, level);
}

HW_State_t HW_IsConfigedActive(HW_Out_t out)
{
    HW_Level_t level;

    level = HW_GetSetted(out);
    return (level == _outs[out].activeLevel) ? HW_State_Active : HW_State_Inactive;
}

bool HW_IsActive(HW_In_t in)
{
    HW_Level_t level;

    level = HW_Get(in);
    return level == _ins[in].activeLevel;
}

bool HW_IsExtiActive(HW_Exti_t exti)
{
    HW_Level_t level;
    uint16_t pin;

    pin = _pinAttribs[_extis[exti].pinIndex].pin;
    level = LL_GPIO_IsInputPinSet(_extis[exti].port, pin) ?
            HW_Level_High : HW_Level_Low;
    return level == _extis[exti].activeLevel;
}

void HW_GPIO_Init(GPIO_TypeDef *port, uint32_t ll_pin, HW_Mode_t mode)
{
	LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

	GPIO_InitStruct.Pin = ll_pin;
	GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
	if(mode <= HW_Mode_OutputOpenDrain)
	{
        GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;
        GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
		if(mode == HW_Mode_OutputPushPull)
		{
			GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
		}
		else
		{
			GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
		}
	}
	else
	{
        GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;
		if(mode == HW_Mode_InputFlow)
		{
			GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
		}
		else if(mode == HW_Mode_InputPullUp)
		{
			GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
		}
		else
		{
			GPIO_InitStruct.Pull = LL_GPIO_PULL_DOWN;
		}
	}

	LL_GPIO_Init(port, &GPIO_InitStruct);
}

#if 0
unsigned int HW_ReadBoardAddres(void)
{
    HW_In_t sn;
    unsigned int addr, d;

    addr = 0;
    d = 0x01;
    for(sn = HW_In_SW1; sn <= HW_In_SWMax; sn++)
    {
        if(HW_IsActive(sn))
        {
            addr |= d;
        }

        d <<= 1;
    }

    return addr;
}
#endif

void HW_SetExtiCallback(HW_Exti_t exti, HW_ExtiCallback_t cb, int data)
{
    uint16_t pinIndex;

    pinIndex = _extis[exti].pinIndex;
    _extiCallbacks[pinIndex].cb = cb;
    _extiCallbacks[pinIndex].data = data;
}

void HW_ExtiCmd(HW_Exti_t exti, FunctionalState state)
{
    if(state == DISABLE)
    {
        LL_EXTI_DisableIT_0_31(_pinAttribs[_extis[exti].pinIndex].extiLine);
    }
    else
    {
        LL_EXTI_EnableIT_0_31(_pinAttribs[_extis[exti].pinIndex].extiLine);
    }
}

static inline void _ExtiSingleIRQHandler(uint16_t pinIndex)
{
    uint16_t pin;

    pin = _pinAttribs[pinIndex].pin;
    if(__HAL_GPIO_EXTI_GET_IT(pin) != RESET)
    {
      __HAL_GPIO_EXTI_CLEAR_IT(pin);
      _extiCallbacks[pinIndex].cb(_extiCallbacks[pinIndex].data);
    }
}

static inline void _ExtiMultipleIRQHandler(int pinIndexFrom, int pinIndexTo)
{
    unsigned int exti;

    for(exti = 0; exti < ARRAY_LEN(_extis); exti++)
    {
        if((_extis[exti].pinIndex < pinIndexFrom) || (_extis[exti].pinIndex > pinIndexTo))
        {
            continue;
        }

        _ExtiSingleIRQHandler(_extis[exti].pinIndex);
    }
}

void EXTI0_IRQHandler(void)
{
	_ExtiSingleIRQHandler(0);
}

void EXTI1_IRQHandler(void)
{
	_ExtiSingleIRQHandler(0);
}

void EXTI2_IRQHandler(void)
{
	_ExtiSingleIRQHandler(0);
}

void EXTI3_IRQHandler(void)
{
	_ExtiSingleIRQHandler(0);
}

void EXTI4_IRQHandler(void)
{
	_ExtiSingleIRQHandler(0);
}

void EXTI9_5_IRQHandler(void)
{
    _ExtiMultipleIRQHandler(5, 9);
}

void EXTI15_10_IRQHandler(void)
{
    _ExtiMultipleIRQHandler(10, 15);
}

static int _ShellCmdIoIn(void *stream, int argc, char *argv[])
{
    HW_In_t in;
    char name[8];
    unsigned int len, i;
    bool isExist;

    if(argc == 1)
    {
        for(in = 0; in < ARRAY_LEN(_ins); in++)
        {
            chprintf(stream, "%s: %s\n", _ins[in].name, HW_Get(in) == HW_Level_High ? "1" : "0");
        }
    }
    else if(argc == 2)
    {
        len = strlen(argv[1]);
        if(len >= ARRAY_LEN(name))
        {
            chprintf(stream, "Unknown IO name!\n");
            return -1;
        }

        for(i = 0; i < len; i++)
        {
            name[i] = toupper(argv[1][i]);
        }
        name[i] = '\0';

        isExist = FALSE;
        for(in = 0; in < ARRAY_LEN(_ins); in++)
        {
            if(strcmp(name, _ins[in].name) == 0)
            {
                chprintf(stream, "%s: %s\n", _ins[in].name, HW_Get(in) == HW_Level_High ? "1" : "0");
                isExist = TRUE;
                break;
            }
        }

        if(! isExist)
        {
            chprintf(stream, "Unknown IO name!\n");
        }
    }

    return 0;
}

SHELL_CMD(in, _ShellCmdIoIn, "Show input pin level", "[<pin name>]");

static int _ShellCmdIoOut(void *stream, int argc, char *argv[])
{
    HW_Out_t out;
    char name[16];
    unsigned int len, i;
    bool isExist;
    int l;

    if(argc == 1)
    {
        for(out = 0; out < ARRAY_LEN(_outs); out++)
        {
            chprintf(stream, "%s: %s\n", _outs[out].name,
                    HW_GetSetted(out) == HW_Level_High ? "1" : "0");
        }
    }
    else if(argc >= 2)
    {
        len = strlen(argv[1]);
        if(len >= ARRAY_LEN(name))
        {
            chprintf(stream, "Unknown IO name!\n");
            return -1;
        }

        for(i = 0; i < len; i++)
        {
            name[i] = toupper(argv[1][i]);
        }
        name[i] = '\0';

        isExist = FALSE;
        for(out = 0; out < ARRAY_LEN(_outs); out++)
        {
            len = strlen(_outs[out].name);
            char cmd[len];
            for(i = 0; i < len; i++)
            {
                cmd[i] = toupper(_outs[out].name[i]);
            }
            cmd[i] = '\0';

            if(strcmp(name, cmd) == 0)
            {
                isExist = TRUE;
                break;
            }
        }

        if(isExist)
        {
            if(argc == 2)
            {
                chprintf(stream, "%s: %s\n", _outs[out].name,
                        HW_GetSetted(out) == HW_Level_High ? "1" : "0");
            }
            else
            {
                l = atoi(argv[2]);
                HW_Set(out, l ? HW_Level_High : HW_Level_Low);
                chprintf(stream, "%s: Set to %s\n", _outs[out].name, l ? "1" : "0");
            }
        }
        else
        {
            chprintf(stream, "Unknown IO name!\n");
        }
    }

    return 0;
}

SHELL_CMD(out, _ShellCmdIoOut, "Show or set output pin level", "[<pin name> [0|1]]");
